package fi.libros.gae;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;


/**
 * Google App Engine Datastore service wrapper.
 * 
 * @author Marko Asplund
 */
public class MappingDatastoreServiceImpl implements MappingDatastoreService {
	private final Logger logger = LoggerFactory.getLogger(MappingDatastoreServiceImpl.class);
	@SuppressWarnings("unchecked")
	Map<String, EntityMapper> mappers = new HashMap<String, EntityMapper>();
	

	public MappingDatastoreServiceImpl() {
	}

	@SuppressWarnings("unchecked")
	public <T> Key put(T obj) {
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		EntityMapper<T> mapper = mappers.get(obj.getClass().getName());
		return ds.put(mapper.mapToEntity(obj));
	}
	
	@SuppressWarnings("unchecked")
	public <T> T get(Key key, Class<T> clazz) throws EntityNotFoundException {
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		EntityMapper<T> mapper = mappers.get(clazz.getName());
		return mapper.mapFromEntity(ds.get(key));
	}

	/*
	 * ==================== utility methods ====================
	 */
	
	@SuppressWarnings("unchecked")
	public void setMapperPackage(String mapperPackage) {
		try {
			for(Class cl : getImplementers(mapperPackage, EntityMapper.class)) {
				Method meth = cl.getMethod("mapFromEntity", Entity.class);
				mappers.put(meth.getReturnType().getName(), (EntityMapper)cl.newInstance());
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to scan EntityMapper classes", e);
		}
		logger.info("mappers: "+mappers);
	}

	@SuppressWarnings("unchecked")
	private List<Class> getImplementers(String packageName, Class intf)
	throws IOException, ClassNotFoundException {
		List<Class> classes = new ArrayList<Class>();
		String dir = packageName.replace('.', '/');
		ClassLoader ldr = this.getClass().getClassLoader();
		for(Enumeration<URL> en = ldr.getResources(dir); en.hasMoreElements(); ) {
			URL u = en.nextElement();
			File d = new File(u.getFile());
			String[] classNames = d.list(new FilenameFilter() {
				@Override
				public boolean accept(File dir, String name) {
					return name.endsWith(".class");
				}
			});
			for(String n : classNames) {
				String fqn = packageName + "." + n.substring(0, n.length()-6);
				Class cl = Class.forName(fqn);
				if(implementsInterface(cl, intf))
					classes.add(cl);
			}
		}
		return classes;
	}
	
	@SuppressWarnings("unchecked")
	private static boolean implementsInterface(Class clazz, Class intf) {
		for(Class cl : clazz.getInterfaces())
			if(intf == cl)
				return true;
		return false;
	}


}
